import { GlDropdown } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
import VulnerabilityStateDropdownDeprecated from 'ee/vulnerabilities/components/vulnerability_state_dropdown_deprecated.vue';
import { VULNERABILITY_STATE_OBJECTS } from 'ee/vulnerabilities/constants';

const stateObjects = Object.values(VULNERABILITY_STATE_OBJECTS);
const states = stateObjects.map((stateObject) => stateObject.state);

describe('Vulnerability state dropdown deprecated component', () => {
  let wrapper;
  let hideDropdownMock;

  const createWrapper = (initialState = states[0]) => {
    hideDropdownMock = jest.fn();

    const GlDropdownStub = stubComponent(GlDropdown, {
      template: RENDER_ALL_SLOTS_TEMPLATE,
      methods: {
        hide: hideDropdownMock,
      },
    });

    wrapper = shallowMountExtended(VulnerabilityStateDropdownDeprecated, {
      propsData: { initialState },
      stubs: { GlDropdown: GlDropdownStub },
    });
  };

  const isSelected = (item) => item.find('.selected-icon').exists();
  const isDisabled = (item) => item.attributes('disabled') === 'true';
  const dropdownItems = () => wrapper.findAll('.dropdown-item');
  const firstUnselectedItem = () => wrapper.find('.dropdown-item:not(.selected)');
  const selectedItem = () => wrapper.find('.dropdown-item.selected');
  const saveButton = () => wrapper.findComponent({ ref: 'save-button' });
  const cancelButton = () => wrapper.findComponent({ ref: 'cancel-button' });
  const innerDropdown = () => wrapper.findComponent(GlDropdown);
  const dropdownItemFor = (state) => wrapper.findByTestId(state);

  describe('tests that need to manually create the wrapper', () => {
    it.each(states)(
      'selects "%s" state when dropdown is created with that initial state',
      (state) => {
        createWrapper(state);

        expect(isSelected(dropdownItemFor(state))).toBe(true);
      },
    );

    it('selects no state when dropdown is created with an unknown initial state', () => {
      createWrapper('some unknown state');

      dropdownItems().wrappers.forEach((dropdownItem) => {
        expect(isSelected(dropdownItem)).toBe(false);
      });
    });

    it.each(states)(`only selects "%s" state when that dropdown item is clicked`, async (state) => {
      createWrapper('some unknown state');
      const dropdownItem = dropdownItemFor(state);

      await dropdownItem.trigger('click');

      dropdownItems().wrappers.forEach((item) => {
        expect(isSelected(item)).toBe(item.attributes('data-testid') === state);
      });
    });
  });

  describe('tests that use the default wrapper', () => {
    beforeEach(() => createWrapper());

    it('enables/disables the save button based on if the selected item has changed or not', async () => {
      const originalItem = selectedItem();

      expect(isDisabled(saveButton())).toBe(true);

      await firstUnselectedItem().trigger('click');

      expect(isDisabled(saveButton())).toBe(false);

      await originalItem.trigger('click');

      expect(isDisabled(saveButton())).toBe(true);
    });

    it('closes the dropdown and fires a change event when clicking the save button', async () => {
      createWrapper();

      expect(isDisabled(saveButton())).toBe(true);

      await firstUnselectedItem().trigger('click');
      saveButton().vm.$emit('click');
      const changeEvent = wrapper.emitted('change');

      expect(hideDropdownMock).toHaveBeenCalledTimes(1);
      expect(changeEvent).toHaveLength(1);
      expect(changeEvent[0][0]).toEqual(expect.any(Object));
    });

    it('closes the dropdown without emitting any events when clicking the cancel button', async () => {
      expect(isDisabled(saveButton())).toBe(true);

      await firstUnselectedItem().trigger('click');

      expect(isDisabled(saveButton())).toBe(false);

      cancelButton().vm.$emit('click');

      expect(Object.keys(wrapper.emitted())).toHaveLength(0);
      expect(hideDropdownMock).toHaveBeenCalledTimes(1);
    });

    it('resets the selected item back to the initial item when the dropdown is closed', async () => {
      const initialSelectedItem = selectedItem();

      await firstUnselectedItem().trigger('click');

      expect(selectedItem().element).not.toBe(initialSelectedItem.element);

      innerDropdown().vm.$emit('hide');
      await nextTick();

      expect(selectedItem().element).toBe(initialSelectedItem.element);
    });

    it('updates the selected and initial item when the parent component changes the state', async () => {
      const stateObject = stateObjects[1];
      await wrapper.setProps({ initialState: stateObject.state });

      expect(innerDropdown().props('text')).toBe(stateObject.buttonText);
      expect(selectedItem().text()).toMatch(stateObject.dropdownText);
      expect(isDisabled(saveButton())).toBe(true);
    });
  });
});
